<?php
/**
 * 异常和错误处理类
 *
 * @version $Id: IErrorHandler.php 312 2008-04-23 16:29:38Z iwind $
 * @package util.core
 */
class IErrorHandler extends IObject {
	private $requestMethod;
	
	private $errorLevels = array(E_ERROR => "E_ERROR", E_WARNING => "E_WARNING", E_PARSE => "E_PARSE", E_NOTICE => "E_NOTICE", E_CORE_ERROR => "E_CORE_ERROR", E_CORE_WARNING => "E_CORE_WARNING", E_COMPILE_ERROR => "E_COMPILE_ERROR", E_COMPILE_WARNING => "E_COMPILE_WARNING", E_USER_ERROR => "E_USER_ERROR", E_USER_WARNING => "E_USER_WARNING", E_USER_NOTICE => "E_USER_NOTICE");
	
	/**
	 * 构造方法
	 *
	 */
	public function __construct() {
		$this->requestMethod = IRequest::singleton()->method();
		
		//错误级别
		if(defined("E_STRICT")) {
			$this->errorLevels[E_STRICT] = "E_STRICT";
		}
	    if(defined("E_RECOVERABLE_ERROR")) {
	    	$this->errorLevels[E_RECOVERABLE_ERROR] = "E_RECOVERABLE_ERROR";
	    }
	}
	
	/**
	 * 异常处理方法
	 * 
	 * 从0.0.3版本起，可以在application.conf.php中配置
	 * <code>
	 *  "exception-handler" => array(
     *		"file" => "../logs/exception.log",
	 *		"stdout" => true
	 *   )
	 * </code>
	 * 
	 * 从0.2开始可以定义每个Exception对应的page
	 *
	 * @param Exception $exception 异常对象
	 */
	public function handlerException($exception) {
		$request = IRequest::singleton();
		$browser = "\n<table border=\"0\" cellspacing=\"1\" bgcolor=\"#eeeeee\" style=\"font:12px 'Courier New'\"><tr><td><strong>I-Framework " . get_class($exception) . " Message:</strong> " . $exception->getMessage() . "<br/>\n</td>\n</tr>\n"
  			  	. "<tr>\n<td>\n<strong>Script:</strong> code <em><strong>" . $exception->getCode() . "</strong></em> on line <em><strong>" . $exception->getLine() . "</strong></em> in file <em><strong>" . $exception->getFile() . "</strong></em></td>\n</tr>\n"
  			  	. "<tr>\n<td>\n<strong>URL:</strong> <em>" . $request->uri() . "</em>"
  			  	. "<tr>\n<td>\n" . nl2br($exception->getTraceAsString()) . "</td>\n</tr>\n"
  			  	. "</table>\n";
  		$cmd = "I-Framework " . get_class($exception) . " Message: " . $exception->getMessage() . "\n"
  			  	. "code " . $exception->getCode() . " on line " . $exception->getLine() . " in file " . $exception->getFile() . "\n"
  			  	. $exception->getTraceAsString();
  			
  		$exceptionHandlers = IConfig::opt("@error.exception");
  		if (!is_array($exceptionHandlers) || !isset($exceptionHandlers["stdout"]) || $exceptionHandlers["stdout"]) {//stdout
			if ($this->requestMethod == "POST" || $this->requestMethod == "GET") { 
				echo $browser;
			}
			else {
				echo $cmd . "\n";
			}
  		}
  		if (isset($exceptionHandlers["log"]) && $exceptionHandlers["log"] != "") {//write to file
  			$file = IApplication::singleton()->relativeFile($exceptionHandlers["log"])->path();
  			$logWriter = new IFileLogWriter($file);
  			$message = "%{a} - %{t} - %{m} - %{U}\n" . $cmd . "\n\n";
  			IRegistry::singleton()->single("ISimpleLogFilter")->doFilter($message);
  			$logWriter->write($message);
  			$logWriter->close();
  		}
  		if (isset($exceptionHandlers["pages"]) && is_array($exceptionHandlers["pages"])) {//show pages
  			$class = strtolower(get_class($exception));
  			$defined = false;
  			foreach ($exceptionHandlers["pages"] as $_class=>$_page) {
  				if (strtolower($_class) == $class) {
  					$defined = $this->goPage($_page);
  				}
  			}
  			if (!$defined && isset($exceptionHandlers["pages"]["default"])) {
  				$this->goPage($exceptionHandlers["pages"]["default"]);
  			}
  		}
	}
	
	/**
	 * Exception跳转页
	 *
	 * @param mixed $page 页面定义
	 * @since 0.2
	 */
	private function goPage($page) {
		if (is_scalar($page)) {
			IResponse::singleton()->redirect($page);
			return true;
		}
		else if (is_array($page)) {
			if (isset($page["redirect"])) {//redirect
				IResponse::singleton()->redirect($page["redirect"]);
				return true;
			}
			elseif(isset($page["forward"])) {//forward
				if (is_file($page["forward"])) {
					require_once($page["forward"]);
					return true;
				}
			}
		}
		return false;
	}
	
	/**
	 * 错误处理方法
	 *
	 * @param integer $errorNo 错误号
	 * @param string $errorStr 错误描述字符串
	 * @param string $errorFile 错误发生的文件
	 * @param string $errorLine 错误发生的行号
	 * @param string $errorContext 错误的上下文
	 * @return boolean
	 */
	public function handlerError($errorNo, $errorStr, $errorFile, $errorLine, $errorContext) {
		//get trace
		$trace = null;
		try {
			throw new Exception(null);
		} catch (Exception $e) {
			$trace = $e->getTraceAsString();
		}
		
		$errorReportingLevel = IConfig::opt("@error.error.level");
		if (error_reporting() == 0) {// mask error by @ operator
			return true;
		}
		if ($errorReportingLevel != E_ALL && !in_array($errorNo, $this->getErrorLevels($errorReportingLevel))) {
			return true;
		}
		if (is_file($errorFile)) {
			$lines = file($errorFile);	
		}
		else {
			$lines = array();
		}
		$description = "";
		$errorIndex = $errorLine - 1;
		$maxLine = 4;
		if ($errorIndex > 0) {
			$preIndex =  max($errorIndex - $maxLine, 0);
			for($lineNo=$preIndex; $lineNo<$errorIndex + $maxLine; $lineNo++) {
				if (isset($lines[$lineNo])) {
					$lines[$lineNo] = $this->format($lines[$lineNo]);
					if ($errorIndex == $lineNo) {
						$description .= "<span style=\"color:red\">" . $lines[$lineNo] . "</span>";
					}
					else {
						$description .= $lines[$lineNo];
					}
				}
			}
		}
		
		$browser = $message = "\n<table border=\"0\" cellspacing=\"1\" bgcolor=\"#eeeeee\" style=\"font:12px 'Courier New'\"><tr><td><strong>I-Framework " . $this->errorLevels[$errorNo] . " Message:</strong> " . $errorStr . "<br/>\n</td>\n</tr>\n"
	  			  . "<tr>\n<td>\n<strong>Script:</strong> code <em><strong>" . $errorNo . "</strong></em> on line <em><strong>" . $errorLine . "</strong></em> in file <em><strong>" . $errorFile . "</strong></em></td>\n</tr>\n"
	  			  . "<tr>\n<td>\n<strong>URL:</strong> <em>" . IRequest::singleton()->uri() . "</em>"
	  			  . "<tr>\n<td><strong>Source:</strong></td></tr>"
	  			  . "<tr>\n<td>-------------------------------------------------------------------</td></tr>"
	  			  . "<tr>\n<td><code>\n" . nl2br($description) . "</code></td>\n</tr>\n"
	  			  . "<tr>\n<td>-------------------------------------------------------------------</td></tr>"
	  			  . "<tr>\n<td>" . nl2br($trace) . "</td>\n</tr>\n"
	  			  . "</table>\n";;
	  	$cmd = "I-Framework " . $this->errorLevels[$errorNo]  . " Message: " . $errorStr . "\n"
	  			  . "code " . $errorNo . " on line " . $errorLine . " in file " . $errorFile . "\n" . $trace . "\n";
		
		if (IConfig::opt("@error.error.stdout")) {
			if ($this->requestMethod == "POST" || $this->requestMethod == "GET") { 
				echo $browser;
			}
			else {
				echo $cmd;
			}
		}
		
		//logfile
		$logFile = IConfig::opt("@error.error.log");
		if ($logFile != null) {
			$logFile = IApplication::singleton()->relativeFile($logFile)->path();
  			$logWriter = new IFileLogWriter($logFile);
  			$logWriter->write($cmd . "\n");
		}
		
		return true;
	}
	
	private function format($text) {
		$text = htmlspecialchars($text);
		$text = str_replace(" ", "&nbsp;", $text);
		$text = str_replace("	", "&nbsp;&nbsp;&nbsp;&nbsp;", $text);
		return $text;
	}
	
	/**
	 * 根据级别取得支持的错误级别
	 *
	 * @param integer $level 级别
	 * @return array
	 * @since 0.2
	 */
	private function getErrorLevels($level) {
	    $names = array_keys($this->errorLevels);
	    $levels = array();
	    if(($level & E_ALL) == E_ALL) {
	        $levels[] = E_ALL;
	        $level &= ~E_ALL;
	    }
	    foreach($names as $name) {
	        if(($level & $name) == $name) {
	        	$levels[] = $name;
	        }
	    }
	    return $levels;
	}
}


?>